﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using Roslyn.Compilers.CSharp;

namespace InterfaceWeaver.CustomTool
{
    public class AspectDispatcher
    {
        /// <summary>
        /// Gets the parsed code.
        /// </summary>
        public ParsedCodeContext ParsedCodeContext { get; private set; }

        /// <summary>
        /// Gets the weaved code.
        /// </summary>
        public WeavedCodeContext WeavedCodeContext { get; private set; }

        /// <summary>
        /// Gets the namespace attribute context.
        /// </summary>
        public IEnumerable<AttributeSyntax> NamespaceAttributesContext { get; private set; }

        /// <summary>
        /// Gets the interface attribute context.
        /// </summary>
        public IEnumerable<AttributeSyntax> InterfaceAttributeContext { get; private set; }

        /// <summary>
        /// The chain of responsibility
        /// </summary>
        private readonly IList<IAspectHandler> chainOfResponsibility = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="AspectDispatcher" /> class.
        /// </summary>
        /// <param name="parsedCode">The parsed code.</param>
        /// <param name="weavedCode">The weaved code.</param>
        public AspectDispatcher(
            CompilationUnitSyntax parsedCode,
            CodeCompileUnit weavedCode)
        {
            this.ParsedCodeContext = new ParsedCodeContext(parsedCode);
            this.WeavedCodeContext = new WeavedCodeContext(weavedCode);
            this.chainOfResponsibility = AspectConfiguration.GetRegisteredAspectHandlers();
        }

        /// <summary>
        /// Registers the handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        public void RegisterHandler(IAspectHandler handler)
        {
            if(!this.chainOfResponsibility.Contains(handler))
            {
                this.chainOfResponsibility.Add(handler);
            }
        }

        /// <summary>
        /// Manages the namespace syntax.
        /// </summary>
        /// <param name="namespaceDeclarationSyntax">The namespace declaration syntax.</param>
        /// <param name="defaultAttributes">The default attributes.</param>
        public void ManageNamespaceSyntax(NamespaceDeclarationSyntax namespaceDeclarationSyntax, IEnumerable<AttributeSyntax> defaultAttributes)
        {
            this.ParsedCodeContext.ChangeContext(namespaceDeclarationSyntax);
            this.WeavedCodeContext.ChangeContext(new NamespaceDeclaration(namespaceDeclarationSyntax)
                .CreateCodeNamespace(this.ParsedCodeContext.ParsedCode));

            // Initialize context at Interface level
            var attributes = this.ParsedCodeContext.ParsedCode.AttributeLists.SelectMany(attr => attr.Attributes).ToList();

            foreach (var defaultAttribute in defaultAttributes)
            {
                if(attributes.All(a => a.Name != defaultAttribute.Name))
                {
                    attributes.Add(defaultAttribute);
                }
            }

            this.NamespaceAttributesContext = attributes;
            this.InterfaceAttributeContext = null;

            foreach (var attributeSyntax in attributes)
            {
                var isHandled = this.chainOfResponsibility
                    .Any(aspectHandler => aspectHandler.HandleOnNamespace(
                        this,
                        attributeSyntax,
                        namespaceDeclarationSyntax));

                if (!isHandled)
                {
                    this.WeavedCodeContext
                        .WeavedCode
                        .AssemblyCustomAttributes
                        .Add(attributeSyntax.CreateAttributeDeclaration());
                }
            }
        } 

        /// <summary>
        /// Manages the interface syntax.
        /// </summary>
        /// <param name="interfaceDeclarationSyntax">The interface declaration syntax.</param>
        public void ManageInterfaceSyntax(InterfaceDeclarationSyntax interfaceDeclarationSyntax)
        {
            this.ParsedCodeContext.ChangeContext(interfaceDeclarationSyntax);
            this.WeavedCodeContext.ChangeContext(new InterfaceDeclaration(interfaceDeclarationSyntax).CreateCodeTypeImplementation());

            // Initialize context at Interface level
            var attributes = interfaceDeclarationSyntax.AttributeLists
                .SelectMany(attr => attr.Attributes).ToList();
            this.InterfaceAttributeContext = attributes;


            // Recovering direct attributes on current interface
            var interfaceAttributes = interfaceDeclarationSyntax.AttributeLists
                .SelectMany(attr => attr.Attributes).ToList();

            // Recovering indirect attribute defined on parent namespace
            // that have not already been defined on interface
            var additionalNamespaceAttributes = this.NamespaceAttributesContext
                .FilterAdditionalFrom(interfaceAttributes).ToList();


            // preview step: Determine wich attribute (on interface) should be considered as aspect requiering handling
            var concernedDirectHandlers = chainOfResponsibility
                .ChainPreviewFilter(this, interfaceAttributes, handler => handler.HandleOnInterfacePreview).ToList();

            // Determine wich indirect attribute (from namespace) should be considered as aspect requiering handling
            var concernedIndirectHandlers = chainOfResponsibility
                .ChainPreviewFilter(this, additionalNamespaceAttributes, handler => handler.HandleOnInterfacePreview).ToList();


            // Compatibility from namespace considering interface handlers
            concernedIndirectHandlers = concernedIndirectHandlers
                .ChainCompatibilityFilter(this,
                    concernedDirectHandlers,
                    handler => handler.IsIndirectAspectsCompliantOnProperty).ToList();

            // Compacting the finale handler stream
            var streamHandlers = concernedDirectHandlers
                .Union(concernedIndirectHandlers)
                .OrderBy(tuple => tuple.Handler.GetHandlingPriorityOnInterface(
                    this,
                    tuple.Attribute,
                    interfaceDeclarationSyntax)).ToArray();

            // Handling step for each direct handlers
            foreach (var handler in streamHandlers)
            {
                handler.Handler.HandleOnInterface(
                    this,
                    handler.Attribute,
                    interfaceDeclarationSyntax);
            }

            // Write back direct attributes that have not been recognized
            this.WeavedCodeContext
                .CurrentTypeDeclaration
                .CustomAttributes.AddRange(
                    interfaceAttributes
                        .Where(attribute => concernedDirectHandlers.All(tuple => tuple.Attribute != attribute))
                        .Select(attribute => attribute.CreateAttributeDeclaration())
                        .ToArray());
        } 

        internal AspectTuple[] GetPropertyAspectTupleStream(
            PropertyDeclarationSyntax propertyDeclarationSyntax,
            out AttributeSyntax[] unknownAttributesToDeclare)
        {
            this.ParsedCodeContext.ChangeContext(propertyDeclarationSyntax);

            // Recovering direct attributes on current property
            var memberAttributes = propertyDeclarationSyntax.AttributeLists
                .SelectMany(attr => attr.Attributes).ToList();

            // Recovering indirect attribute defined on parent interface
            // that have not already been defined on property
            var additionalInterfaceAttributes = this.InterfaceAttributeContext
                .FilterAdditionalFrom(memberAttributes).ToList();

            // Recovering indirect attribute defined on parent namespace
            // that have not already been defined neither on property nor on parent interface
            var additionalNamespaceAttributes = this.NamespaceAttributesContext
                .FilterAdditionalFrom(memberAttributes, additionalInterfaceAttributes).ToList();


            // Determine wich attribute (on property) should be considered as aspect requiering handling
            var concernedDirectHandlers = chainOfResponsibility
                .ChainPreviewFilter(this, memberAttributes, handler => handler.HandleOnPropertyPreview).ToList();

            // Determine wich indirect attribute (from interface) should be considered as aspect requiering handling
            var concernedIndirectInterfaceHandlers = chainOfResponsibility
                .ChainPreviewFilter(this, additionalInterfaceAttributes, handler => handler.HandleOnPropertyPreview).ToList();

            // Determine wich indirect attribute (from namespace) should be considered as aspect requiering handling
            var concernedIndirectNamespaceHandlers = chainOfResponsibility
                .ChainPreviewFilter(this, additionalNamespaceAttributes, handler => handler.HandleOnPropertyPreview).ToList();


            // Compatibility from interface considering property handlers
            concernedIndirectInterfaceHandlers = concernedIndirectInterfaceHandlers
                .ChainCompatibilityFilter(this,
                    concernedDirectHandlers,
                    handler => handler.IsIndirectAspectsCompliantOnProperty).ToList();

            // Compatibility from namespace considering interface handlers
            concernedIndirectNamespaceHandlers = concernedIndirectNamespaceHandlers
                .ChainCompatibilityFilter(this,
                    concernedIndirectInterfaceHandlers,
                    handler => handler.IsIndirectAspectsCompliantOnProperty).ToList();

            // Compatibility from namespace considering property handlers
            concernedIndirectNamespaceHandlers = concernedIndirectNamespaceHandlers
                .ChainCompatibilityFilter(this,
                    concernedDirectHandlers,
                    handler => handler.IsIndirectAspectsCompliantOnProperty).ToList();

            // Creation of the unknown attributes
            unknownAttributesToDeclare = memberAttributes
                .Where(attribute => concernedDirectHandlers.All(tuple => tuple.Attribute != attribute))
                //.Select(attribute => attribute.CreateAttributeDeclaration())
                .ToArray();

            // Compacting the finale handler stream
            return concernedDirectHandlers
                .Union(concernedIndirectInterfaceHandlers)
                .Union(concernedIndirectNamespaceHandlers)
                .OrderBy(tuple => -(int)tuple.Handler.GetHandlingPriorityOnProperty(
                    this,
                    tuple.Attribute,
                    propertyDeclarationSyntax)).ToArray();
        }

        internal void HandlePropertySyntax(
            PropertyDeclarationSyntax propertyDeclarationSyntax, 
            IDictionary<PropertyDeclarationSyntax, AspectTuple[]> streamHandlers,
            AttributeSyntax[] unknownAttributesToDeclare)
        {
            this.ParsedCodeContext.ChangeContext(propertyDeclarationSyntax);
            this.WeavedCodeContext.ChangeContext(new PropertyDeclaration(propertyDeclarationSyntax).CreateCodePropertyImplementation());

            // Handling step for each direct handlers
            foreach (var handler in streamHandlers[propertyDeclarationSyntax])
            {
                handler.Handler.HandleOnProperty(
                    this,
                    handler.Attribute,
                    propertyDeclarationSyntax,
                    streamHandlers);
            }

            // Write back direct attributes that have not been recognized
            this.WeavedCodeContext
                .CurrentCodeMemberPropertyDeclaration
                .CurrentCodeMemberPropertyDeclaration
                .CustomAttributes.AddRange(
                    unknownAttributesToDeclare
                    .Select(attribute => attribute.CreateAttributeDeclaration())
                    .ToArray());
        }

        /// <summary>
        /// Handles the event syntax.
        /// </summary>
        /// <param name="eventDeclarationSyntax">The event declaration syntax.</param>
        /// <param name="streamHandlers">The stream handlers.</param>
        /// <param name="unknownAttributesToDeclare">The unknown attributes to declare.</param>
        internal void HandleEventSyntax(
            EventDeclarationSyntax eventDeclarationSyntax,
            IDictionary<EventDeclarationSyntax, AspectTuple[]> streamHandlers,
            AttributeSyntax[] unknownAttributesToDeclare)
        {
            this.ParsedCodeContext.ChangeContext(eventDeclarationSyntax);
            this.WeavedCodeContext.ChangeContext(new EventDeclaration(eventDeclarationSyntax).CreateCodeEventImplementation());

            // Write back direct attributes that have not been recognized
            this.WeavedCodeContext
                .CurrentCodeMemberPropertyDeclaration
                .CurrentCodeMemberPropertyDeclaration
                .CustomAttributes.AddRange(
                    unknownAttributesToDeclare
                    .Select(attribute => attribute.CreateAttributeDeclaration())
                    .ToArray());
        }
    }

    internal static class AspectDispatcherHelper
    {
        /// <summary>
        /// Filters the additional from source attributes.
        /// </summary>
        /// <param name="additionalAttributes">The additional attributes.</param>
        /// <param name="attributesSource">The attributes source.</param>
        /// <returns></returns>
        public static IEnumerable<AttributeSyntax> FilterAdditionalFrom(
            this IEnumerable<AttributeSyntax> additionalAttributes, 
            IEnumerable<AttributeSyntax> attributesSource)
        {
            return new List<AttributeSyntax>(
               from interfaceAttribute in additionalAttributes
               let interfaceAttributeName = interfaceAttribute.Name.ToFullString().Trim()
               where attributesSource.All(attr => interfaceAttributeName != attr.Name.ToFullString().Trim())
               select interfaceAttribute);
        }

        /// <summary>
        /// Filters the additional from source attributes.
        /// </summary>
        /// <param name="additionalAttributes">The additional attributes.</param>
        /// <param name="attributesSource1">The attributes source1.</param>
        /// <param name="attributesSource2">The attributes source2.</param>
        /// <returns></returns>
        public static IEnumerable<AttributeSyntax> FilterAdditionalFrom(
            this IEnumerable<AttributeSyntax> additionalAttributes,
            IEnumerable<AttributeSyntax> attributesSource1,
            IEnumerable<AttributeSyntax> attributesSource2)
        {
            return new List<AttributeSyntax>(
               from interfaceAttribute in additionalAttributes
               let namespaceAttributeName = interfaceAttribute.Name.ToFullString().Trim()
               where attributesSource1.All(attr => namespaceAttributeName != attr.Name.ToFullString().Trim())
                    && attributesSource2.All(attr => namespaceAttributeName != attr.Name.ToFullString().Trim())
               select interfaceAttribute);
        }

        /// <summary>
        /// Chains the preview filter.
        /// </summary>
        /// <param name="chainOfResponsibility">The chain of responsibility.</param>
        /// <param name="aspectDispatcher">The aspect dispatcher.</param>
        /// <param name="attributes">The attributes.</param>
        /// <param name="previewPredicate">The preview predicate.</param>
        /// <returns></returns>
        public static IEnumerable<AspectTuple> ChainPreviewFilter(
            this IEnumerable<IAspectHandler> chainOfResponsibility, 
            AspectDispatcher aspectDispatcher,
            IEnumerable<AttributeSyntax> attributes,
            Func<IAspectHandler, Func<AspectDispatcher,AttributeSyntax ,bool>> previewPredicate)
        {
            return attributes
                .Select(attribute => new AspectTuple
                {
                    Handler = chainOfResponsibility.FirstOrDefault(handler => previewPredicate(handler)(aspectDispatcher, attribute)),//aspectHandler.HandleOnPropertyPreview(this, attribute)),
                    Attribute = attribute
                })
                .Where(tuple => tuple.Handler != null);
        }

        /// <summary>
        /// Chains the compatibility filter.
        /// </summary>
        /// <param name="filteredtuples">The filteredtuples.</param>
        /// <param name="aspectDispatcher">The aspect dispatcher.</param>
        /// <param name="directHandlerTuples">The direct handler tuples.</param>
        /// <param name="compatibilityPredicate">The compatibility predicate.</param>
        /// <returns></returns>
        public static IEnumerable<AspectTuple> ChainCompatibilityFilter(
            this IEnumerable<AspectTuple> filteredtuples,
            AspectDispatcher aspectDispatcher,
            IList<AspectTuple> directHandlerTuples,
            Func<IAspectHandler, Func<AspectDispatcher, AttributeSyntax, IAspectHandler, AttributeSyntax, bool>> compatibilityPredicate)
        {
            var resultingConcernedTuples = new List<AspectTuple>(filteredtuples);
            foreach (var tuple in directHandlerTuples)
            {
                var currentHandler = tuple;
                var handlersToRemove = resultingConcernedTuples
                    .Where(indirectHandler => !compatibilityPredicate(currentHandler.Handler)(
                        aspectDispatcher,
                        currentHandler.Attribute,
                        indirectHandler.Handler,
                        indirectHandler.Attribute)).ToList();

                foreach (var tupleToRemove in handlersToRemove)
                {
                    resultingConcernedTuples.Remove(tupleToRemove);
                }
            }

            return resultingConcernedTuples;
        }
    }

    public struct AspectTuple
    {
        public IAspectHandler Handler { get; set; }
        public AttributeSyntax Attribute { get; set; }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Format("Tuple_{0}", Attribute);
        }
    }
}
